/****************************************************************************
 * Copyright (C) 2011
 * by Dimok
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event will the authors be held liable for any
 * damages arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any
 * purpose, including commercial applications, and to alter it and
 * redistribute it freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you
 * must not claim that you wrote the original software. If you use
 * this software in a product, an acknowledgment in the product
 * documentation would be appreciated but is not required.
 *
 * 2. Altered source versions must be plainly marked as such, and
 * must not be misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any source
 * distribution.
 ***************************************************************************/
#include <malloc.h>
#include <unistd.h>
#include <string.h>
#include <ogc/mutex.h>
#include <ogc/system.h>
#include <sdcard/wiisd_io.h>
#include <sdcard/gcsd.h>
#include <ogc/machine/processor.h>

#include "../FileOperations/Nand.hpp"
#include "../System/runtimeiospatch.h"
#include "DeviceHandler.hpp"
#include "../Settings/CSettings.h"

#define USB_REG_BASE				0x0D040000
#define USB_REG_OP_BASE				(USB_REG_BASE + (read32(USB_REG_BASE) & 0xff))
#define USB_REG_PORTSC_OFF(port)	(0x44 + 4 * port)
#define USB_PORT_CONNECTED(port)	(read32(USB_REG_OP_BASE + USB_REG_PORTSC_OFF(port)) & 0x0F)

DeviceHandler * DeviceHandler::instance = NULL;

DeviceHandler::~DeviceHandler()
{
	UnMountAll();
	UnMountAllGC();
	
	USBStorage2_Deinit();
}

DeviceHandler * DeviceHandler::Instance()
{
	if (instance == NULL)
		instance = new DeviceHandler();
	
	return instance;
}

void DeviceHandler::DestroyInstance()
{
	if(instance)
		delete instance;
	
	instance = NULL;
}

bool DeviceHandler::MountAll()
{
	bool result = false;

	for(u32 i = SD; i <= USB9; i++)
	{
		if(Mount(i))
			result = true;
	}

	return result;
}

void DeviceHandler::UnMountAll()
{
	for(u32 i = SD; i <= USB9; i++)
		UnMount(i);

	if(sd)
		delete sd;
	if(usb0)
		delete usb0;
	if(usb1)
		delete usb1;
	
	sd = NULL;
	usb0 = NULL;
	usb1 = NULL;
}

bool DeviceHandler::Mount(int dev)
{
	if(dev == SD)
		return MountSD();

	else if(dev == GCA)
		return MountGCA();

	else if(dev == GCB)
		return MountGCB();

	else if(dev >= USB1 && dev <= USB9)
		return MountUSB(dev-USB1);

	else if(dev == NAND)
		return Nand::Startup();

	return false;
}

bool DeviceHandler::MountAllGC()
{
	MountGCA();
	MountGCB();
	
	if(gca || gcb)
		return true;
	
	return false;
}

bool DeviceHandler::MountGCA()
{
	if(!gca)
		gca = new MemCardHandle(CARD_SLOTA);

	if(!gca->IsInserted() || !gca->Mount())
	{
		delete gca;
		gca = NULL;
		return false;
	}

	return true;
}

bool DeviceHandler::MountGCB()
{
	if(!gcb)
		gcb = new MemCardHandle(CARD_SLOTB);

	if(!gcb->IsInserted() || !gcb->Mount())
	{
		delete gcb;
		gcb = NULL;
		return false;
	}

	return true;
}

bool DeviceHandler::IsInserted(int dev)
{
	if(dev == SD)
		return SD_Inserted() && sd->IsMounted(0);

	else if(dev == GCA)
		return GCA_Inserted() && gca->IsMounted();

	else if(dev == GCB)
		return GCB_Inserted() && gcb->IsMounted();

	else if(dev >= USB1 && dev <= USB9)
		return GetUSBFromDev(dev) && GetUSBFromDev(dev)->IsMounted(PartToPortPart(dev-USB1));

	else if(dev == NAND || dev == WII || dev == MII)
		return Nand::IsInitialized();
	
	else if(dev == EWII1 || dev == EMII1 ||
			dev == EWII2 || dev == EMII2 ||
			dev == EWII3 || dev == EMII3)
	{
		std::string emuPath = "";
		if(dev == EWII1 || dev == EMII1)
			emuPath = Settings.EmuNandPath_1;
		else if(dev == EWII2 || dev == EMII2)
			emuPath = Settings.EmuNandPath_2;
		else if(dev == EWII3 || dev == EMII3)
			emuPath = Settings.EmuNandPath_3;
		
		if(emuPath.empty())
			return false;
		
		int emuDev = PathToDriveType(emuPath);
		
		if(emuDev == SD)
			return (SD_Inserted() && sd->IsMounted(0) && !Settings.SneekRunning && Settings.EmuBrowsers);
		else
			return (GetUSBFromDev(emuDev) && GetUSBFromDev(emuDev)->IsMounted(PartToPortPart(emuDev-USB1)) && !Settings.SneekRunning && Settings.EmuBrowsers);
	}

	return false;
}

void DeviceHandler::UnMount(int dev)
{
	if(dev == SD)
		UnMountSD();

	else if(dev == GCA)
		UnMountGCA();

	else if(dev == GCB)
		UnMountGCB();

	else if(dev >= USB1 && dev <= USB9)
		UnMountUSB(dev-USB1);

	else if(dev == NAND)
		Nand::Shutdown();
}

bool DeviceHandler::MountSD()
{
	if(!sd)
		sd = new PartitionHandle(&__io_wiisd);

	if(sd->GetPartitionCount() < 1)
	{
		delete sd;
		sd = NULL;
		return false;
	}

	//! Mount only one SD Partition
	return sd->Mount(0, DeviceName[SD]);
}

bool DeviceHandler::MountNAND()
{
	return Nand::Startup();
}

void DeviceHandler::UnMountNAND()
{
	Nand::Shutdown();
}

bool DeviceHandler::MountUSB(int pos)
{
	if(!usb0 && Settings.USBPort != 1)
		usb0 = new PartitionHandle((IOS_GetVersion() > 200) ? &__io_usbstorage2_port0 : &__io_usbstorage);
	if(!usb1 && Settings.USBPort >= 1 && IOS_GetVersion() > 200)
		usb1 = new PartitionHandle(&__io_usbstorage2_port1);

	int partCount = 0;
	if(usb0)
		partCount += usb0->GetPartitionCount();
	if(usb1)
		partCount += usb1->GetPartitionCount();

	if(pos >= partCount)
		return false;

	return GetUSBFromDev(USB1+pos)->Mount(PartToPortPart(pos), DeviceName[USB1+pos]);
}

bool DeviceHandler::MountAllUSB(bool spinUp)
{
	if(spinUp && !USBSpinUp(10))
		return false;
	
	if(!usb0 && Settings.USBPort != 1)
		usb0 = new PartitionHandle((IOS_GetVersion() > 200) ? &__io_usbstorage2_port0 : &__io_usbstorage);
	if(!usb1 && Settings.USBPort > 1 && IOS_GetVersion() > 200)
		usb1 = new PartitionHandle(&__io_usbstorage2_port1);

	if(usb0 && !usb0->GetPartitionCount())
	{
		delete usb0;
		usb0 = NULL;
	}
	if(usb1 && !usb1->GetPartitionCount())
	{
		delete usb1;
		usb1 = NULL;
	}

	bool result = false;
	int partCount = 0;
	if(usb0)
		partCount += usb0->GetPartitionCount();
	if(usb1)
		partCount += usb1->GetPartitionCount();

	for(int i = 0; i < partCount; i++)
	{
		if(MountUSB(i))
			result = true;
	}

	return result;
}

bool DeviceHandler::MountUSBPort0(bool spinUp)
{
	bool usb1mounted = false;
	if(usb1)
	{
		UnMountAllUSB();
		usb1mounted = true;
	}

	if(spinUp && !USBSpinUp(10))
		return false;

	if(!usb0 && Settings.USBPort != 1)
		usb0 = new PartitionHandle((IOS_GetVersion() > 200) ? &__io_usbstorage2_port0 : &__io_usbstorage);
	
	if(usb0 && !usb0->GetPartitionCount())
	{
		delete usb0;
		usb0 = NULL;
		return false;
	}

	bool result = false;
	int partCount = 0;
	if(usb0)
		partCount = usb0->GetPartitionCount();
	
	for(int i = 0; i < partCount; i++)
	{
		if(MountUSB(i))
			result = true;
	}
	
	if(usb1mounted)
		MountUSBPort1();

	return result;
}

bool DeviceHandler::MountUSBPort1(bool spinUp)
{
	if(spinUp && !USBSpinUp(10))
		return false;

	if(!usb1 && Settings.USBPort > 1 && IOS_GetVersion() > 200)
		usb1 = new PartitionHandle(&__io_usbstorage2_port1);

	if(usb1 && !usb1->GetPartitionCount())
	{
		delete usb1;
		usb1 = NULL;
		return false;
	}

	bool result = false;
	int partCount0 = 0;
	if(usb0)
		partCount0 = usb0->GetPartitionCount();
	int partCount = partCount0 + usb1->GetPartitionCount();
	
	for(int i = partCount0; i < partCount; i++)
	{
		if(MountUSB(i))
			result = true;
	}

	return result;
}

void DeviceHandler::UnMountUSB(int pos)
{
	if(GetUSBFromDev(USB1+pos))
		GetUSBFromDev(USB1+pos)->UnMount(PartToPortPart(pos));
}

void DeviceHandler::UnMountAllUSB()
{
	int partCount = 0;
	if(usb0)
		partCount += usb0->GetPartitionCount();
	if(usb1)
		partCount += usb1->GetPartitionCount();

	for(int i = 0; i < partCount; i++)
		UnMountUSB(i);

	if(usb0)
		delete usb0;
	if(usb1)
		delete usb1;
	usb0 = NULL;
	usb1 = NULL;
	
	USBStorage2_Deinit();
}

void DeviceHandler::UnMountUSBPort0()
{
	if(!usb0)
		return;
	
	bool usb1mounted = false;
	if(usb1)
		usb1mounted = true;

	UnMountAllUSB();
	
	if(usb1mounted)
		MountUSBPort1();
}

void DeviceHandler::UnMountUSBPort1()
{
	if(!usb1)
		return;
	
	int partCount0 = 0;
	if(usb0)
		partCount0 = usb0->GetPartitionCount();
	int partCount = usb1->GetPartitionCount();

	for(int i = partCount0; i < partCount; i++)
		UnMountUSB(i);

	if(usb1)
		delete usb1;
	usb1 = NULL;
}

const DISC_INTERFACE *DeviceHandler::GetUSB0Interface(void)
{
	return (IOS_GetVersion() > 200) ? &__io_usbstorage2_port0 : &__io_usbstorage;
}

const DISC_INTERFACE *DeviceHandler::GetUSB1Interface(void)
{
	if(IOS_GetVersion() < 200)
		return NULL;

	return &__io_usbstorage2_port1;
}

bool DeviceHandler::USBSpinUp(int iTimeout)
{
	//! if we have AHBPROT access, check if some device is connected to the ports before
	//! trying to mount them. Otherwise just try and wait till it times out
	if(AHBPROT_DISABLED)
	{
		if((Settings.USBPort == 0) && !USB_PORT_CONNECTED(0))
			return false;
		else if ((Settings.USBPort == 1) && !USB_PORT_CONNECTED(1))
			return false;
		else if (!USB_PORT_CONNECTED(0) && !USB_PORT_CONNECTED(1))
			return false;
	}
	
	bool started0 = false;
	bool started1 = false;
	time_t tStart = time(0);
	
	const DISC_INTERFACE * handle0 = NULL;
	const DISC_INTERFACE * handle1 = NULL;
	if(Settings.USBPort != 1)
		handle0 = DeviceHandler::GetUSB0Interface();
	if(Settings.USBPort != 0)
		handle1 = DeviceHandler::GetUSB1Interface();
	
	do
	{
		if(handle0)
			started0 = (handle0->startup() && handle0->isInserted());
		
		if(handle1)
			started1 = (handle1->startup() && handle1->isInserted());
		
		if ((!handle0 || started0) &&
			(!handle1 || started1))
			break;
		
		usleep(50000);
	}
	while(time(0) - tStart < iTimeout);
	
	return (started0 || started1);
}

PartitionHandle * DeviceHandler::GetUSBFromDev(int dev)
{
	int usbPart = dev-USB1;

	int partCount0 = 0;
	if(usb0)
		partCount0 = usb0->GetPartitionCount();

	if(!usb0 || usbPart >= partCount0)
		return usb1;
	else
		return usb0;
}

 int DeviceHandler::PartToPortPart(int part)
 {
	int partCount0 = 0;
	if(usb0)
		partCount0 = usb0->GetPartitionCount();

	if(!usb0 || part >= partCount0)
		return part-partCount0;
	else
		return part;
 }

 int DeviceHandler::PartToPort(int part)
 {
	int partCount0 = 0;
	if(usb0)
		partCount0 = usb0->GetPartitionCount();

	if(!usb0 || part > partCount0)
		return 1;
	else
		return 0;
 }

int DeviceHandler::PathToDriveType(std::string path)
{
	if(path.empty())
		return -1;
	
	for(int i = SD; i <= USB9; i++)
	{
		if(!path.compare(0, strlen(DeviceName[i]), DeviceName[i]))
			return i;
	}

	return -1;
}

const char * DeviceHandler::GetFSName(int dev)
{
	if(dev == SD && DeviceHandler::instance->sd)
	{
		return DeviceHandler::instance->sd->GetFSName(0);
	}
	else if(dev >= USB1 && dev <= USB9 && instance->GetUSBFromDev(dev))
	{
		return instance->GetUSBFromDev(dev)->GetFSName(instance->PartToPortPart(dev-USB1));
	}

	return NULL;
}
